package com.grand.security.distributed.uaa.config;

import java.util.Arrays;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

@Configuration
// 开启授权服务
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
	@Autowired
	private TokenStore tokenStore;
	@Autowired
	private JwtAccessTokenConverter accessTokenConverter;
	@Autowired
	private ClientDetailsService clientDetailsService;
	@Autowired
	private AuthorizationCodeServices authorizationCodeServices;
	@Autowired
	private AuthenticationManager authenticationManager;

	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
	
	/**
	 * AuthorizationServerTokenServices接口定义了一些操作使得你可以对令牌进行一些必要的管理，
	 * 令牌可以被用来加载身份信息，里面包含了这个令牌的相关权限
	 */
	@Bean
	public AuthorizationServerTokenServices tokenService() {
		// DefaultTokenServices类几乎做了令牌管理的所有工作，除了持久化令牌需要委托给一个TokenStore接口来实现；
		DefaultTokenServices service = new DefaultTokenServices();
		service.setClientDetailsService(clientDetailsService);
		service.setSupportRefreshToken(true);
		service.setTokenStore(tokenStore);
		
		TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
		tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
		service.setTokenEnhancer(tokenEnhancerChain);
		
		service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时
		service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天
		return service;
	}

	@Bean
	public ClientDetailsService clientDetailsService(DataSource dataSource){
		JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
		clientDetailsService.setPasswordEncoder(passwordEncoder());
		return clientDetailsService;
	}
	
	public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
		security
			.tokenKeyAccess("permitAll()")
			.checkTokenAccess("permitAll()")
			.allowFormAuthenticationForClients();
	}

	/**
	 * ClientDetailsServiceConfigure能够使用内存或者JDBC来实现客户端详情服务（ClientDetailsService）
	 * ClientDetailsService负责查找ClientDetails，而ClientDetails有几个重要的属性
	 * <li>clientId: （必须的）用来标识客户的ID</li>
	 * <li>secret: (需要值得信任的客户端)客户端安全码，如果有的话</li>
	 * <li>scope：用来限制客户端的访问范围，如果为空（默认）的话，那么客户端拥有全部的访问范围</li>
	 * <li>authorizedGrantTypes：此客户端可以使用的授权类型，默认为空</li>
	 * <li>authorities：此客户端可以使用的权限（基于Spring Security authorities）</li>
	 */
	@Override
	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
		clients.withClientDetails(clientDetailsService);
//		clients.inMemory()// 使用in-memory存储
//				.withClient("c1")// client_id
//				.secret(new BCryptPasswordEncoder().encode("secret")).resourceIds("res1")
//				.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
//				// 该client允许的授权类型
//				// authorization_code,password,refresh_token,implicit,client_credentials
//				.scopes("all")// 允许的授权范围
//				.autoApprove(false)
//				// 加上验证回调地址
//				.redirectUris("http://www.baidu.com");
	}

	/**
	 * 令牌访问断点配置
	 * AuthorizationServerEndpointsConfigurer实例，可以完成令牌服务以及令牌endpoint配置
	 */
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
		endpoints
			// 认证管理器，当你选择了资源所有者密码授权类型的时候，请设置这个属性注入一个authenticationManager对象
			.authenticationManager(authenticationManager)
			// 设置授权码服务用的
			.authorizationCodeServices(authorizationCodeServices)
			.tokenServices(tokenService())
			.allowedTokenEndpointRequestMethods(HttpMethod.POST);
	}
	
	@Bean
	public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) { 
		//设置授权码模式的授权码如何存取，暂时采用内存方式
		// return new InMemoryAuthorizationCodeServices();
		return new JdbcAuthorizationCodeServices(dataSource);
	}

}
